﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO.Ports;

namespace 温湿度和颗粒度监控测试.Common.Modbus
{
    public class ModbusSerialPort
    {
        public ModbusSerialPort()
        {
            serialPort = new SerialPort();
        }

        /// <summary>
        /// 使用的串口
        /// </summary>
        private SerialPort serialPort;

        /// <summary>
        /// 串口名
        /// </summary>
        public string PortName { get; set; }

        /// <summary>
        /// 波特率
        /// </summary>
        public int BaudRate { get; set; }

        /// <summary>
        /// 数据位
        /// </summary>
        public int DataBits { get; set; }

        /// <summary>
        /// 校验位 默认为None
        /// </summary>
        public Parity Parity { get; set; } = Parity.None;

        /// <summary>
        /// 停止位 默认为One
        /// </summary>
        public StopBits StopBits { get; set; } = StopBits.One;

        /// <summary>
        /// 打开串口
        /// </summary>
        private void Open()
        {
            if (serialPort == null) throw new ApplicationException("serialPort不能为空");
            if (serialPort.IsOpen) return;

            serialPort.PortName = !string.IsNullOrEmpty(PortName) ? PortName : throw new ApplicationException("缺少参数PortName，无法打开串口");
            serialPort.BaudRate = BaudRate == default ? BaudRate : throw new ApplicationException("缺少参数BaudRate，无法打开串口");
            serialPort.DataBits = DataBits == default ? DataBits : throw new ApplicationException("缺少参数DataBits，无法打开串口");
            serialPort.Parity = Parity;
            serialPort.StopBits = StopBits;
            serialPort.Open();
        }


        /// <summary>
        /// 读保持寄存器
        /// </summary>
        /// <param name="stationNo"></param>
        /// <param name="startAddress"></param>
        /// <param name="readCount"></param>
        /// <returns></returns>
        public ModbusResult ReadHoldingRegister(byte stationNo, UInt16 startAddress, UInt16 readCount, CRC16 isCrc = CRC16.Close)
        {
            List<byte> pduMessage = new List<byte>();
            pduMessage.Add(0x03);   // 功能码
            pduMessage.AddRange(BitConverter.GetBytes(startAddress).Reverse());   // 起始地址
            pduMessage.AddRange(BitConverter.GetBytes(readCount).Reverse());  // 读取数量

            List<byte> message = GenerateRequestProtocolMessage(stationNo, pduMessage);
            if (isCrc == CRC16.Open)  // 是否增加CRC校验
                message.AddRange(CRC16_MODBUS_Formated(message));

            byte[] receiveBuffer = new byte[1024];
            receiveBuffer[7] = 0x03;
            //byte[] receiveBuffer = SendMessage(message.ToArray());

            // 错误码
            if (receiveBuffer[7] != 0x03)
            {
                string errorMessage = $"{receiveBuffer[0]:X2} {receiveBuffer[1]:X2} {receiveBuffer[2]:X2} {receiveBuffer[3]:X2} {receiveBuffer[4]:X2} {receiveBuffer[5]:X2} {receiveBuffer[6]:X2} {receiveBuffer[7]:X2} {receiveBuffer[8]:X2}";
                for (int i = 0; i < Convert.ToUInt16(receiveBuffer[8]); i++)
                {
                    errorMessage += $" {receiveBuffer[i + 9]:X2}";
                }
                throw new ApplicationException($"MudBus通讯异常，返回的报文：{errorMessage}");
            }

            int[] result = new int[readCount];
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = BitConverter.ToInt16(receiveBuffer, i * 2 + 9);
            }

            return new ModbusResult()
            {
                SendMessage = message,
                ReceiveMessage = GetReceiveMessage(receiveBuffer),
                ReadResult = result,
            };
        }

        /// <summary>
        /// 读取保持寄存器（无协议头）
        /// </summary>
        /// <param name="stationNo"></param>
        /// <param name="startAddress"></param>
        /// <param name="readCount"></param>
        /// <param name="isCrc"></param>
        /// <returns></returns>
        public ModbusResult ReadHoldingRegisterNoHeader(byte stationNo, UInt16 startAddress, UInt16 readCount, CRC16 isCrc = CRC16.Close)
        {
            List<byte> pduMessage = new List<byte>();
            pduMessage.Add(0x03);   // 功能码
            pduMessage.AddRange(BitConverter.GetBytes(startAddress).Reverse());   // 起始地址
            pduMessage.AddRange(BitConverter.GetBytes(readCount).Reverse());  // 读取数量

            List<byte> message = GenerateRequestProtocolMessage(stationNo, pduMessage, ProtocolHeader.Not);
            if (isCrc == CRC16.Open)  // 是否增加CRC校验
                message.AddRange(CRC16_MODBUS_Formated(message));

            byte[] receiveBuffer = new byte[1024];
            receiveBuffer[2] = 0x03;
            //byte[] receiveBuffer = SendMessage(message.ToArray());

            // 错误码
            if (receiveBuffer[2] != 0x03)
            {
                string errorMessage = $"{receiveBuffer[0]:X2} {receiveBuffer[1]:X2} {receiveBuffer[2]:X2} {receiveBuffer[3]:X2} {receiveBuffer[4]:X2} {receiveBuffer[5]:X2} {receiveBuffer[6]:X2} {receiveBuffer[7]:X2} {receiveBuffer[8]:X2}";
                for (int i = 0; i < Convert.ToUInt16(receiveBuffer[8]); i++)
                {
                    errorMessage += $" {receiveBuffer[i + 9]:X2}";
                }
                throw new ApplicationException($"MudBus通讯异常，返回的报文：{errorMessage}");
            }

            int[] result = new int[readCount];
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = BitConverter.ToInt16(receiveBuffer, i * 2 + 9);
            }

            return new ModbusResult()
            {
                SendMessage = message,
                ReceiveMessage = GetReceiveMessage(receiveBuffer),
                ReadResult = result,
            };
        }


        /// <summary>
        /// 获取接收到的报文
        /// </summary>
        /// <param name="receiveBuffer"></param>
        /// <returns></returns>
        List<byte> GetReceiveMessage(byte[] receiveBuffer)
        {
            List<byte> result = new List<byte>();

            int byteCount = Convert.ToInt32(receiveBuffer[8]);

            result.AddRange(receiveBuffer.Take(9 + byteCount));

            return result;
        }

        /// <summary>
        /// 发送报文
        /// </summary>
        /// <param name="message"></param>
        private byte[] SendMessage(byte[] message, int readDelay = 100)
        {
            if (serialPort == null) throw new ApplicationException("未打开串口");
            if (!serialPort.IsOpen) Open();

            serialPort.Write(message, 0, message.Length);
            Thread.Sleep(readDelay);
            int n = serialPort.BytesToRead;
            byte[] receiveMessage = new byte[n];

            serialPort.Read(receiveMessage, 0, n);
            Thread.Sleep(readDelay);

            return receiveMessage;
        }

        /// <summary>
        /// 生成请求报文
        /// </summary>
        /// <param name="pduMessage"></param>
        /// <returns></returns>
        List<byte> GenerateRequestProtocolMessage(byte stationNo, List<byte> pduMessage, ProtocolHeader haveHeader = ProtocolHeader.Have)
        {
            List<byte> message = new List<byte>();
            if (haveHeader == ProtocolHeader.Have)
            {
                message.Add(0x12);
                message.Add(0x04);   // 消息号，可以随意指定
                message.Add(0x00);
                message.Add(0x00);  // Modbus标识，强制为0
                message.Add(0x00);
                message.Add(BitConverter.GetBytes(pduMessage.Count + 1)[0]);    // 标识byte[5]之后的字节数
            }
            
            message.Add(stationNo); // 站号
            message.AddRange(pduMessage);

            return message;
        }

        #region CRC校验部分
        /// <summary>
        /// 封装CRC校验位获取函数
        /// </summary>
        /// <param name="data">CRC校验数据</param>
        /// <returns></returns>
        public static byte[] CRC16_MODBUS_Formated(List<byte> data)
        {
            ushort crcValue = CRC16_MODBUS(data.ToArray());
            return new byte[2] { (byte)(crcValue % 256), (byte)(crcValue / 256) };
        }

        /// <summary>
        /// 获取CRC校验值
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static ushort CRC16_MODBUS(byte[] bytes)
        {
            ushort value;
            ushort newLoad = 0xffff, In_value;
            int count = 0;
            for (int i = 0; i < bytes.Length; i++)
            {
                value = (ushort)bytes[i];
                newLoad = (ushort)(Convert.ToInt32(value) ^ Convert.ToInt32(newLoad));
                In_value = 0xA001;
                while (count < 8)
                {
                    if (Convert.ToInt32(newLoad) % 2 == 1)//判断最低位是否为1
                    {
                        newLoad -= 0x00001;
                        newLoad = (ushort)(Convert.ToInt32(newLoad) / 2);//右移一位
                        count++;//计数器加一
                        newLoad = (ushort)(Convert.ToInt32(newLoad) ^ Convert.ToInt32(In_value));//异或操作
                    }
                    else
                    {
                        newLoad = (ushort)(Convert.ToInt32(newLoad) / 2);//右移一位
                        count++;//计数器加一
                    }
                }
                count = 0;
            }
            return newLoad;
        }
        #endregion
    }

    /// <summary>
    /// 有无CRC校验
    /// </summary>
    public enum CRC16
    {
        Open = 1,
        Close = 2,
    }

    /// <summary>
    /// 协议头有无
    /// </summary>
    public enum ProtocolHeader
    {
        Have = 1,
        Not = 2,
    }

    /// <summary>
    /// ModBus通讯后返回的结果
    /// </summary>
    public class ModbusResult
    {
        /// <summary>
        /// 发送的报文
        /// </summary>
        public List<byte> SendMessage { get; set; }

        /// <summary>
        /// 接收的报文
        /// </summary>
        public List<byte> ReceiveMessage { get; set; }

        /// <summary>
        /// 读取的结果
        /// </summary>
        public int[] ReadResult { get; set; }

        /// <summary>
        /// 生成输入信息
        /// </summary>
        /// <returns></returns>
        public string GenerateOutputMessage()
        {
            string sendMessage = "";
            if (SendMessage != null && SendMessage.Count > 0)
            {
                foreach (byte item in SendMessage)
                {
                    sendMessage += $" {item:X2}";
                }
            }

            string receiveMessage = "";
            if (ReceiveMessage != null && ReceiveMessage.Count > 0)
            {
                foreach (byte item in ReceiveMessage)
                {
                    receiveMessage += $" {item:X2}";
                }
            }

            DateTime now = DateTime.Now;
            string message = "";
            if (!string.IsNullOrEmpty(sendMessage))
            {
                message += $"[{now:HH:mm:ss}]发→◇{sendMessage}\r\n";
            }
            if (!string.IsNullOrEmpty(receiveMessage))
            {
                message += $"[{now:HH:mm:ss}]收←◆{receiveMessage}\r\n";
            }

            // 转换后得到的数据
            if (ReadResult != null)
            {
                string readResult = "";
                foreach (int item in ReadResult)
                {
                    readResult += $" {item}";
                }
                message += $"[{now:HH:mm:ss}]读取结果←◆{readResult}\r\n";
            }

            return message;
        }
    }
}
